package com.dianrong.common.uniauth.client.custom.multitenancy;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.dianrong.common.uniauth.client.custom.UserExtInfo;
import com.dianrong.common.uniauth.client.custom.UserInfoCallBack;
import com.dianrong.common.uniauth.client.custom.model.UserExtInfoParam;
import com.dianrong.common.uniauth.client.support.CheckDomainDefine;
import com.dianrong.common.uniauth.common.bean.Response;
import com.dianrong.common.uniauth.common.bean.dto.DomainDto;
import com.dianrong.common.uniauth.common.bean.dto.PermissionDto;
import com.dianrong.common.uniauth.common.bean.dto.RoleDto;
import com.dianrong.common.uniauth.common.bean.dto.UserDetailDto;
import com.dianrong.common.uniauth.common.bean.dto.UserDto;
import com.dianrong.common.uniauth.common.bean.request.LoginParam;
import com.dianrong.common.uniauth.common.client.DomainDefine;
import com.dianrong.common.uniauth.common.client.UniClientFacade;
import com.dianrong.common.uniauth.common.server.cxf.CxfHeaderHolder;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SSMultiTenancyUserDetailService implements MultiTenancyUserDetailsService {
    @Autowired
    private UniClientFacade uniClientFacade;

    @Autowired
    private DomainDefine domainDefine;

    @Autowired(required = false)
    private UserInfoCallBack userInfoCallBack;

    @Override
    public UserDetails loadUserByUsername(String userName, long tenancyId) throws UsernameNotFoundException, DataAccessException {
        CxfHeaderHolder.TENANCYID.set(tenancyId);
        try {
            String currentDomainCode = domainDefine.getDomainCode();
            String userInfoClass = domainDefine.getUserInfoClass();

            CheckDomainDefine.checkDomainDefine(currentDomainCode);

            if (userName == null || "".equals(userName.toString())) {
                throw new UsernameNotFoundException(userName + " not found");
            } else {
                LoginParam loginParam = new LoginParam();
                loginParam.setAccount(userName);
                loginParam.setTenancyId(tenancyId);
                // not login, need set tenancyId manually
                Response<UserDetailDto> response = uniClientFacade.getUserResource().getUserDetailInfo(loginParam);
                UserDetailDto userDetailDto = response.getData();

                if (userDetailDto == null) {
                    throw new UsernameNotFoundException(userName + " not found");
                } else {
                    UserDto userDto = userDetailDto.getUserDto();
                    Long id = userDto.getId();
                    List<DomainDto> domainDtoList = userDetailDto.getDomainList();
                    // DomainDto currentDomainDto = null;
                    Map<String, UserExtInfoParam> userExtInfos = new HashMap<>();
                    if (domainDtoList != null && !domainDtoList.isEmpty()) {
                        List<DomainDto> _domainDtoList = null;
                        if (!domainDefine.isUseAllDomainUserInfoShareMode()) {
                            _domainDtoList = new ArrayList<DomainDto>();
                            for (DomainDto domainDto : domainDtoList) {
                                String domainCode = domainDto.getCode();
                                if (currentDomainCode.equals(domainCode)) {
                                    _domainDtoList.add(domainDto);
                                    break;
                                }
                            }
                        } else {
                            _domainDtoList = domainDtoList;
                        }
                        for (DomainDto domainDto : _domainDtoList) {
                            Map<String, Set<String>> permMap = new HashMap<String, Set<String>>();
                            Map<String, Set<PermissionDto>> permDtoMap = new HashMap<>();
                            Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                            List<RoleDto> roleDtoList = domainDto.getRoleList();
                            if (roleDtoList != null && !roleDtoList.isEmpty()) {
                                for (RoleDto roleDto : roleDtoList) {
                                    String roleCode = roleDto.getRoleCode();
                                    SimpleGrantedAuthority authority = new SimpleGrantedAuthority(roleCode);
                                    authorities.add(authority);
                                    mergePermMap(permMap, roleDto.getPermMap());
                                    mergePermMap(permDtoMap, roleDto.getPermDtoMap());
                                }
                            }
                            UserExtInfoParam userExtInfoParam = new UserExtInfoParam();
                            userExtInfoParam.setUsername(userName).setPassword("fake_password").setEnabled(true).setAccountNonExpired(true).setCredentialsNonExpired(true)
                                    .setAccountNonLocked(true).setAuthorities(authorities).setId(id).setUserDto(userDto).setDomainDto(domainDto).setPermMap(permMap)
                                    .setPermDtoMap(permDtoMap);
                            userExtInfos.put(domainDto.getCode(), userExtInfoParam);
                        }
                    }
                    UserExtInfoParam currentDomainUserInfo = userExtInfos.get(currentDomainCode);
                    if (currentDomainUserInfo == null) {
                        UserExtInfoParam userExtInfoParam = new UserExtInfoParam();
                        userExtInfoParam.setUsername(userName).setPassword("fake_password").setEnabled(true).setAccountNonExpired(true).setCredentialsNonExpired(true)
                                .setAccountNonLocked(true).setAuthorities(new ArrayList<GrantedAuthority>()).setId(id).setUserDto(userDto)
                                .setDomainDto(new DomainDto().setCode(DomainDefine.getStaticDomainCode())).setPermMap(new HashMap<String, Set<String>>())
                                .setPermDtoMap(new HashMap<String, Set<PermissionDto>>());
                        userExtInfos.put(currentDomainCode, userExtInfoParam);
                        currentDomainUserInfo = userExtInfos.get(currentDomainCode);
                    }
                    if (userInfoClass == null || "".equals(userInfoClass.trim())) {
                        return UserExtInfo.build(currentDomainUserInfo, userExtInfos);
                    } else {
                        try {
                            Class<?> clazz = Class.forName(userInfoClass);
                            Constructor<?> construct = clazz.getConstructor(String.class, String.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Collection.class,
                                    Long.class, UserDto.class, DomainDto.class, Map.class, Map.class);
                            UserExtInfo userExtInfo = (UserExtInfo) construct.newInstance(currentDomainUserInfo.getUsername(), currentDomainUserInfo.getPassword(),
                                    currentDomainUserInfo.isEnabled(), currentDomainUserInfo.isAccountNonExpired(), currentDomainUserInfo.isCredentialsNonExpired(),
                                    currentDomainUserInfo.isAccountNonLocked(), currentDomainUserInfo.getAuthorities(), currentDomainUserInfo.getId(),
                                    currentDomainUserInfo.getUserDto(), currentDomainUserInfo.getDomainDto(), currentDomainUserInfo.getPermMap(),
                                    currentDomainUserInfo.getPermDtoMap());
                            if (userInfoCallBack != null) {
                                userInfoCallBack.fill(userExtInfo);
                            }
                            return userExtInfo;
                        } catch (Exception e) {
                            log.error("Prepare to use ss-client's UserExtInfo, not the subsystem's customized one, possible reasons:\n (1) " + userInfoClass + " not found. \n (2) "
                                    + userInfoClass + " is not a instance of UserExtInfo.\n (3) userInfoCallBack.fill(userExtInfo) error.", e);
                            return UserExtInfo.build(currentDomainUserInfo, userExtInfos);
                        }
                    }
                }
            }
        } finally {
            CxfHeaderHolder.TENANCYID.set(null);
        }
    }

    private <T> void mergePermMap(Map<String, Set<T>> permMap, Map<String, Set<T>> subPermMap) {
        Set<Entry<String, Set<T>>> subEntrySet = subPermMap.entrySet();
        Iterator<Entry<String, Set<T>>> subEntryIterator = subEntrySet.iterator();
        while (subEntryIterator.hasNext()) {
            Entry<String, Set<T>> subEntry = subEntryIterator.next();
            String permTypeName = subEntry.getKey();
            Set<T> permValueSet = subEntry.getValue();

            if (permMap.containsKey(permTypeName)) {
                permMap.get(permTypeName).addAll(permValueSet);
            } else {
                permMap.put(permTypeName, permValueSet);
            }
        }
    }

}
